package org.gitman.agentplatform.core;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;

import org.gitman.agentplatform.agent.api.Agent;
import org.gitman.agentplatform.agent.api.AgentPlan;
import org.gitman.agentplatform.agent.api.AgentState;
import org.gitman.agentplatform.agent.api.InternalAgentInfo;
import org.gitman.agentplatform.model.Graph;

/**
 * Agent manager has knowledge about all agents in the simulation, 
 * it is also responsible for handling agents movement and messages
 * as well as for managing their parameters:
 * reliablity - 0 - 1.0 chance that agents move will be made
 * obervability - number of vertices visible around agent
 * predictability - numbers of future steps planned by agent to be shared
 * 
 * @author gitman
 *
 */
public class AgentManager {	
	private Map<Agent, InternalAgentInfo> agents;
	private Queue<Agent> agentQueue;
	private Double defaultReliab;
	private Integer defaultMinObserv;
	private Integer defaultMinPredict;
	private Integer defaultMaxObserv;
	private Integer defaultMaxPredict;
	private boolean useDefReliab;
	private boolean useDefObserv;
	private boolean useDefPredict;
	private Integer nextID;
	private PlatformManager platformManager;
	
	public AgentManager(PlatformManager platformManager){
		agents = new HashMap<Agent, InternalAgentInfo>();
		defaultReliab = 1.0;
		defaultMinObserv = Integer.MAX_VALUE; 
		defaultMaxObserv = Integer.MAX_VALUE;
		defaultMinPredict = Integer.MAX_VALUE;
		defaultMaxPredict = Integer.MAX_VALUE;
		nextID = 0;
		this.platformManager = platformManager;
		useDefObserv = false;
		useDefPredict = false;
		useDefReliab = false;
	}
	public boolean isUseDefReliab() {
		return useDefReliab;
	}
	public void setUseDefReliab(boolean useDefReliab) {
		this.useDefReliab = useDefReliab;
	}
	public boolean isUseDefObserv() {
		return useDefObserv;
	}
	public void setUseDefObserv(boolean useDefObserv) {
		this.useDefObserv = useDefObserv;
	}
	public boolean isUseDefPredict() {
		return useDefPredict;
	}
	public void setUseDefPredict(boolean useDefPredict) {
		this.useDefPredict = useDefPredict;
	}		
	public Collection<Agent> getAgents(){
		return agents.keySet();
	}	
	public void addAgent(InternalAgentInfo info, Class<? extends Agent> clazz, String args){
		Agent agent = platformManager.getAgentImpl(clazz, args);
		if(info.getId() != null){
			agent.setId(info.getId());
		}else{
			Integer id = getNextID();
			agent.setId(id);
			info.setId(id);
		}
		agent.setAgentInfo(info);
		agents.put(agent, info);
		if(agentQueue != null){
			agent.setGraph(platformManager.getSimulation().getGraph());
			info.setCurrentVertexID(info.getStartVertexID());
			info.setIteration(0);
			if(!info.isTargetAchived()){
				agentQueue.add(agent);
				info.setAgentState(AgentState.working);
			}else{
				info.setAgentState(AgentState.done);
			}
		}
	}	
	public Double getDefaultReliab() {
		return defaultReliab;
	}
	public void setDefaultReliab(Double defaultReliab) {
		this.defaultReliab = defaultReliab;
	}	
	public Integer getDefaultMinObserv() {
		return defaultMinObserv;
	}
	public void setDefaultMinObserv(Integer defaultMinObserv) {
		this.defaultMinObserv = defaultMinObserv;
	}
	public Integer getDefaultMinPredict() {
		return defaultMinPredict;
	}
	public void setDefaultMinPredict(Integer defaultMinPredict) {
		this.defaultMinPredict = defaultMinPredict;
	}
	public Integer getDefaultMaxObserv() {
		return defaultMaxObserv;
	}
	public void setDefaultMaxObserv(Integer defaultMaxObserv) {
		this.defaultMaxObserv = defaultMaxObserv;
	}
	public Integer getDefaultMaxPredict() {
		return defaultMaxPredict;
	}
	public void setDefaultMaxPredict(Integer defaultMaxPredict) {
		this.defaultMaxPredict = defaultMaxPredict;
	}
	public void initializeAgentQueue(Graph graph){
		agentQueue = new LinkedList<Agent>();
		for(Agent agent : agents.keySet()){
			agent.setGraph(graph);
			InternalAgentInfo info = agents.get(agent);
			info.setCurrentVertexID(info.getStartVertexID());
			info.setIteration(0);
			if(!info.isTargetAchived()){
				agentQueue.add(agent);
				HashMap<Integer[], Double> plan = new HashMap<Integer[], Double>();
				plan.put(new Integer[0], 1.0);
				info.setAgentPlan(new AgentPlan(plan));
				info.setAgentState(AgentState.working);
			}else{
				info.setAgentState(AgentState.done);
			}
		}
		Collections.sort((List<Agent>) agentQueue);
		platformManager.getView().update();
	}
	public void randomizeAgentOrder(){
		LinkedList<Agent> old = (LinkedList<Agent>) agentQueue;
		agentQueue = new LinkedList<Agent>();
		Random rand = new Random(Calendar.getInstance().getTimeInMillis());
		while(!old.isEmpty()){
			int id = rand.nextInt(old.size());
			agentQueue.add(old.remove(id));
		}
	}
	public Agent nextAgent(){
		return agentQueue.poll();
	}
	private Integer getNextID(){
		boolean idOk = true;
		do{
			nextID++;
			idOk = true;
			for(Agent agent : agents.keySet()){
				if(agent.getId().equals(nextID)){
					idOk = false;
				}
			}
		}while(!idOk);
		return nextID;
	}
	public boolean isAgentQueueEmpty(){
		return agentQueue.isEmpty();
	}
	public Queue<Agent> getAgentQueue(){
		return agentQueue;
	}
	public InternalAgentInfo getAgentInfo(Agent agent) {
		return agents.get(agent);
	}
	public Collection<InternalAgentInfo> getAllAgentsInfo() {
		return agents.values();
	}
	public Collection<InternalAgentInfo> getQueuedAgentsInfo(){
		Collection<InternalAgentInfo> infos = new ArrayList<InternalAgentInfo>();
		for(Agent a : agentQueue){
			infos.add(agents.get(a));
		}	
		return infos;
	}
	public void addAgent2Queue(Agent agent){
		agentQueue.add(agent);
	}
	public void removeAgent(Integer id) {
		for(Agent a : agents.keySet()){
			if(a.getId().equals(id)){
				agents.remove(a);
				if(agentQueue != null){
					agentQueue.remove(a);
				}
				return;
			}
		}		
	}
	public InternalAgentInfo getAgentInfo(Integer id){
		for(InternalAgentInfo info : agents.values()){
			if(info.getId().equals(id)){
				return info;
			}
		}		
		return null;
	}
	public Agent getAgent(Integer id){
		for(Agent agent : agents.keySet()){
			if(id.equals(agent.getId())){
				return agent;
			}
		}
		return null;
	}
	public String getObservString() {
		if(useDefObserv){
			String min = defaultMinObserv == Integer.MAX_VALUE ? "inf" : defaultMinObserv.toString();
			String max = defaultMaxObserv == Integer.MAX_VALUE ? "inf" : defaultMaxObserv.toString();
			if(defaultMinObserv != defaultMaxObserv){
				return "(" + min + ";" + max + ")";
			}
			return defaultMinObserv.toString();
		}
		return "not used";
	}
	public String getPredictString() {
		if(useDefPredict){
			String min = defaultMinPredict == Integer.MAX_VALUE ? "inf" : defaultMinPredict.toString();
			String max = defaultMaxPredict == Integer.MAX_VALUE ? "inf" : defaultMaxPredict.toString();
			if(defaultMinPredict != defaultMaxPredict){
				return "(" + min + ";" + max + ")";
			}
			return defaultMinPredict.toString();
		}
		return "not used";
	}
	public String getRelieString() {
		if(useDefReliab){
			return defaultReliab.toString();
		}
		return "not used";
	}
	public void setObserv(boolean use, Integer min, Integer max){
		useDefObserv = use;
		defaultMinObserv = min;
		defaultMaxObserv = max;
	}
	public void setPredict(boolean use, Integer min, Integer max){
		useDefPredict = use;
		defaultMinPredict = min;
		defaultMaxPredict = max;
	}
	public void setRelie(boolean use, Double val){
		useDefReliab = use;
		defaultReliab = val;
	}
}
	